/*
MIT License

Copyright (c) 2018 Meng Rao <raomeng1@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#pragma once
#include "msg_header.h"

namespace tcpshm {

template<uint32_t Bytes>
class SPSCVarQueue
{
public:
  // 块数量，必须是2的幂次方，以便使用位运算优化取模操作
  static constexpr uint32_t BLK_CNT = Bytes / 64;
  static_assert(BLK_CNT && !(BLK_CNT & (BLK_CNT - 1)), "BLK_CNT must be a power of 2");

  /**
   * @brief 分配指定大小的消息空间
   * @param size 消息体大小（不包括消息头）
   * @return MsgHeader* 指向消息头的指针，如果空间不足则返回nullptr
   * 
   * 该函数用于在队列中分配一块指定大小的空间用于存储消息。
   * 它会检查是否有足够的空间，并在必要时进行内存重绕以避免碎片。
   */
  MsgHeader* Alloc(uint16_t size) {
    // 计算包含消息头的总大小
    size += sizeof(MsgHeader);
    // 计算需要占用的块数
    uint32_t blk_sz = (size + sizeof(Block) - 1) / sizeof(Block);
    // 计算当前写入位置到缓存行边界的填充大小
    uint32_t padding_sz = BLK_CNT - (write_idx % BLK_CNT);
    // 判断是否需要重绕（当前块无法容纳消息）
    bool rewind = blk_sz > padding_sz;
    // 计算最小读取索引，用于检查空间是否足够
    // min_read_idx could be a negtive value which results in a large unsigned int
    uint32_t min_read_idx = write_idx + blk_sz + (rewind ? padding_sz : 0) - BLK_CNT;
    // 检查是否有足够的空间
    if ((int)(read_idx_cach - min_read_idx) < 0) {
      asm volatile("" : "=m"(read_idx) : :); // force read memory
      read_idx_cach = read_idx;
      if ((int)(read_idx_cach - min_read_idx) < 0) { // no enough space
        return nullptr;
      }
    }
    // 如果需要重绕，标记当前块为结束块并更新写入索引
    if (rewind) {
      blk[write_idx % BLK_CNT].header.size = 0;
      asm volatile("" : : "m"(blk), "m"(write_idx) :); // memory fence
      write_idx += padding_sz;
    }
    // 设置消息头大小并返回指针
    MsgHeader& header = blk[write_idx % BLK_CNT].header;
    header.size = size;
    return &header;
    }

    /**
     * @brief 提交已分配的消息空间
     * 
     * 将已写入数据的消息提交到队列中，使其对读取方可见。
     * 该函数会更新写入索引并确保内存操作的顺序性。
     */
    void Push() {
        asm volatile("" : : "m"(blk), "m"(write_idx) :); // memory fence
        // 计算当前消息占用的块数
        uint32_t blk_sz = (blk[write_idx % BLK_CNT].header.size + sizeof(Block) - 1) / sizeof(Block);
        // 更新写入索引
        write_idx += blk_sz;
        asm volatile("" : : "m"(write_idx) : ); // force write memory
    }

    /**
     * @brief 获取队列前端的消息
     * @return MsgHeader* 指向队列前端消息的指针，如果队列为空则返回nullptr
     * 
     * 该函数用于查看队列中的第一个未读消息，但不从队列中移除它。
     * 如果遇到重绕标记，则会自动跳过并继续查找有效消息。
     */
    MsgHeader* Front() {
        asm volatile("" : "=m"(write_idx), "=m"(blk) : :); // force read memory
        // 检查队列是否为空
        if(read_idx == write_idx) {
            return nullptr;
        }
        // 获取消息大小
        uint16_t size = blk[read_idx % BLK_CNT].header.size;
        // 检查是否为重绕标记
        if(size == 0) { // rewind
            read_idx += BLK_CNT - (read_idx % BLK_CNT);
            // 再次检查队列是否为空
            if(read_idx == write_idx) {
                return nullptr;
            }
        }
        // 返回消息头指针
        return &blk[read_idx % BLK_CNT].header;
    }

    /**
     * @brief 从队列中移除前端消息
     * 
     * 该函数用于从队列中移除已处理的消息，更新读取索引。
     * 确保内存操作的顺序性以保证多线程环境下的正确性。
     */
    void Pop() {
        asm volatile("" : "=m"(blk) : "m"(read_idx) :); // memory fence
        // 计算当前消息占用的块数
        uint32_t blk_sz = (blk[read_idx % BLK_CNT].header.size + sizeof(Block) - 1) / sizeof(Block);
        // 更新读取索引
        read_idx += blk_sz;
        asm volatile("" : : "m"(read_idx) : ); // force write memory
    }

private:
  /**
   * @brief 内部队列块结构
   * 
   * 每个块大小为64字节，与缓存行大小相同，以提高缓存效率。
   * 使用alignas(64)确保块对齐到缓存行边界。
   */
  struct Block // size of 64, same as cache line
  {
    alignas(64) MsgHeader header;
  } blk[BLK_CNT];

  // 写入索引，使用alignas(128)确保与其他变量不在同一缓存行，减少false sharing
  alignas(128) uint32_t write_idx = 0;
  // 读取索引缓存，仅由写入线程使用，用于减少对读取索引的访问
  uint32_t read_idx_cach = 0; // used only by writing thread

  // 读取索引，使用alignas(128)确保与其他变量不在同一缓存行，减少false sharing
  alignas(128) uint32_t read_idx = 0;
};
} // namespace tcpshm
